#!/usr/bin/env python
# encoding: utf-8
"""
server.py

Created by  on 2007-11-01.

"""

import sys
import pygame

import netc
import entity

# net_str format: "[h][data]"
# h is an ASCII net code header, data is a string


NET_HEADER_CODES = [
    "connect",
    "ping",
    "data",
    "disconnect",
    "kill"
    ]
NET_CODE_OFFSET = 64

#Converts a unicode string header into a descriptive string code.
def decode_net_header(net_str):
    try:
        return NET_HEADER_CODES[ord(net_str[:1])-NET_CODE_OFFSET]
    except:
        print "Invalid net header decoded in: "+net_str
        return False

#Converts a string code to a net-ready unicode header.
def encode_net_header(net_header_code):
    try:
        return chr(NET_HEADER_CODES.index(net_header_core)+NET_CODE_OFFSET)
    except:
        print "Invalid net header encoded: "+net_header_code
        return False

class Server:

    def __init__(self, in_port, out_port, startup_environments):
        self.entity_handler = None      #Handler for all existing game entities
        self.run_game = True            #Runs game loop while this is true
        self.UDP_connection = None      #UDP network connection
        self.clients = []               #Currently connected clients
        self.to_disconnect = []         #Clients to disconnect
        self.clock = None               #Clock to track processing speed
        self.process_speed = 100        #Game loops per second

        pygame.init()
        self.clock = pygame.time.Clock()

        [self.init_environment(env_file) for env_file in startup_environments]

        self.UDP_connection = netc.UDPConn(in_port, out_port)
        print "Server initialized."

        self.run()
        
    def run(self):
        while self.run_game:

            self.to_disconnect = []
            self.to_connect = []
            self.clock.tick(self.process_speed)

            self.get_net_input()
            self.entity_handler.tick(self.to_connect, self.to_disconnect)
            self.send_net_output()

            for client in self.to_disconnect:
                #client.die()
                self.clients.remove(client)

    def get_net_input(self):
        for raw_data, addr in self.UDP_connection.recv_all_data():
            command, params = self.verify_data(raw_data)

            #try:
            if command == "connect":
                self.net_input_actions[command](self, params, addr)
            else:
                client = self.find_client_by_IP(addr)
                if client:
                    self.net_input_actions[command](self, params, client)

            #except:
            #    print "Invalid data received from " + addr + ": "+ raw_data

    def client_connect(self, params, addr):
        #authentication goes here
        client = self.find_client_by_IP(addr)
        if not client:
            name = params[0]
            client = ClientConn(addr, name)
            self.clients.append(client)

            print "Client connected from " + addr
            client.message("connected True")
            self.to_connect.append(client)

        else:
            print "Repeated client connection from " + addr
            client.message("connected False")

    def client_message_from(self, params, client):
        client.incoming_data.append(params)

    def client_pong(self, params, client):
        client.message("pong")
        client.since_last = 0

    def client_disconnect(self, params, client):
        self.to_disconnect.append(client)
        reason = (params[0] if params else "Default")
        print "Client " + client.addr + " disconnected (" + reason + ")"
        client.message("disconnected")

    def client_kill_server(self, params, client):
        print "Server killed from " + client.addr
        self.run_game = False
        [self.client_disconnect([], a_client) for a_client in self.clients]

    #Procedure to execute when a command is received.
    #All these except "connect" must take a list of params and an IP address.
    net_input_actions = {
        "connect":client_connect,
        "data":client_message_from,
        "ping":client_pong,
        "disconnect":client_disconnect,
        "kill":client_kill_server,
        }

    def send_net_output(self):
        for client in self.clients:
            if client.check_timeout(100/self.process_speed):
                self.client_disconnect(["Ping timeout"], client)
            else:
                client.send(self.UDP_connection)
                client.clear_data()

    def init_environment(self, env_file):
        #try:
        self.entity_handler = entity.EntityHandler(100/self.process_speed)
        f = open(env_file, "r")

        for ent in eval(f.read().replace("\x0d",""))["environment"]:
            self.entity_handler.add_entity(ent)

        print "Map loaded: " + env_file
        f.close()
        #except:
        #    print "Error: failed to load " + env_file
        #    return False
        return True

    def del_entity(self, ent):
        self.entities.remove(ent)

    def verify_data(self, data):
        data_words = data.split()
        if data_words:
            header = data_words[0]
            data_words.remove(header)
            return header, data_words
        else:
            return "NULL", []

    def find_client_by_IP(self, addr_to_find):
        for cc in self.clients:
            if cc.addr == addr_to_find:
                return cc
        return False

    def find_client_by_name(self, name_to_find):
        for cc in self.clients:
            if cc.name == name_to_find:
                return cc
        return False

class ClientConn:
    def __init__(self, addr, name):
        self.addr = addr                #IP address
        self.name = name                #Generic name
        self.timeout = 1000             #Standard timeout period, in 100ths of a second
        self.since_last = 0             #Time since last communication
        self.incoming_data = []         #Data just received
        self.outgoing_data = []         #Data to send out

        #self.send_entity = None         #Entity to forward messages to
        #self.receive_entities = []      #Entities to receive messages from

    def check_timeout(self, time_passed):
        if self.incoming_data:
            self.since_last = 0
        else:
            self.since_last += time_passed
        return self.since_last >= self.timeout

    def send(self, connection):
        for data in self.outgoing_data:
            connection.send_data(data, self.addr)

    def message(self, *args):
        msg = ""
        for word in args:
            msg += word + " "
        #msg = msg[0:-2]
        self.outgoing_data.append(msg)

    """
    def die(self):
        if self.send_entity:
            self.send_entity.client = None
            self.send_entity = None

        #move this crap somewhere else
        for ent in self.receive_entities:
            ent.visible_to.remove(self)
        self.receive_entities = []
    """

    def clear_data(self):
        self.outgoing_data = []
        self.incoming_data = []


#Testing only:
#server.py env1filename.env env2filename.env ...

my_server = Server(2202, 2203, ["maps/test_map.map"])